﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using STS.Common;
using STS.Interfaces;
using STS.Schema.Enums;
using STS.Schema.Interfaces;
using System.Collections;
using STS.Common.Enums;

namespace STS.Schema.Factory
{
    public abstract class SchemaFactoryBase : ISchemaFactory
    {
        private Hashtable _innerLists = new Hashtable();

        public List<STSMessage> Messages
        {
            get;
            private set;
        }

        public DbServer Server { get; internal set; }

        public SchemaFactoryBase(DbServer server)
        {
            Server = server;
            Messages = new List<STSMessage>();
        }

        public bool ValidateMapping()
        {
            return true;
        }

        protected bool ValidateConnection()
        {
            if (Server == null || Server.ConnectionInfo == null || string.IsNullOrWhiteSpace(Server.ConnectionInfo.ConnectionString))
            {
                throw new STSException("Invalid connection parameters.");
            }
            return true;
        }

        public ISqlObjectCollection GetObjectList<T>(ISqlObject parent = null, SqlObjectType? sqlObjectType = null, IDatabase database = null) where T : class, ISqlObject, new()
        {
            this.ValidateConnection();
            ISqlObjectCollection list = (ISqlObjectCollection)_innerLists[string.Format("{0}_{1}", typeof(T).Name, database)];
            bool hasErrors = false;

            if (list != null)
            {
                return list;
            }
            try
            {
                list = GetCollection<T>();

                using (DbDataReader reader = DbUtils.ExecuteReader(Server.ConnectionInfo, list.GetCollectionQuery(sqlObjectType, database)))
                {
                    do
                    {
                        while (reader.Read())
                        {
                            T tObj = (T)Activator.CreateInstance(typeof(T), new object[] { this, database });
                            if ((T)tObj == null)
                            {
                                continue;
                            }
                            STSMessage[] messages;
                            SqlPropertyInfo[] properties = SqlPropertyInfo.GetProperties(tObj, reader, out messages);

                            Messages.AddRange(messages);
                            if (!hasErrors && messages.ToList().Exists(item => item.Type == MessageType.Error))
                            {
                                hasErrors = true;
                            }
                            if (parent == null || tObj.ParentObjectID == parent.ObjectID || tObj is ITable || tObj is IView)
                            {
                                list.Add((T)tObj);
                            }
                        }
                    }
                    while (reader.NextResult());
                }
            }
            catch (Exception ex)
            {
                Messages.Add(new STSMessage() { Text = string.Format("Error getting object list of type '{0}'.", typeof(T).Name), Details = ex.GetBaseException().ToString(), Type = MessageType.Error });
                throw;
            }
            if (!hasErrors)
            {
                _innerLists.Add(string.Format("{0}_{1}", typeof(T).Name, database), list);
            }

            return list;
        }

        public ISqlObject GetObject<T>(string name, ISqlObject parent = null, IDatabase database = null) where T : class, ISqlObject, new()
        {
            return GetObjectList<T>(parent, null, database).OfType<T>().FirstOrDefault(item => item.Name == name);
        }

        public ISqlObject GetObject<T>(int id, ISqlObject parent = null, IDatabase database = null) where T : class, ISqlObject, new()
        {
            return GetObjectList<T>(parent, null, database).OfType<T>().FirstOrDefault(item => item.ObjectID == id);
        }

        public virtual void ClearObjectList<T>(IDatabase database = null) where T : class, ISqlObject, new()
        {
            if (_innerLists.ContainsKey(string.Format("{0}_{1}", typeof(T).Name, database)))
            {
                _innerLists.Remove(string.Format("{0}_{1}", typeof(T).Name, database));
            }
        }

        public abstract ISqlObjectCollection GetCollection<T>(ISqlObject parent = null) where T : class, ISqlObject, new();
        public abstract Type GetSqlObjectType(SqlObjectType sqlObjectType);
    }
}
